<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>cannon.js - constraints demo</title>
    <link rel="stylesheet" href="css/style.css" type="text/css" />
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
  </head>
  <body>
    <script type="module">
      import * as CANNON from '../dist/cannon-es.js'
      import { Demo } from './js/Demo.js'

      /**
       * In this demo, we demonstrate some constraint types. Constraints
       * removes degrees of freedom from bodies and forces them to move in
       * a way defined by the constraint.
       */

      const demo = new Demo()

      demo.addScene('Lock', () => {
        const world = setupWorld(demo)
        world.gravity.set(0, -10, 0)
        world.solver.iterations = 20

        const size = 0.5
        const mass = 1
        const space = size * 0.1

        const boxShape = new CANNON.Box(new CANNON.Vec3(size, size, size))

        const N = 10
        let previous
        for (let i = 0; i < N; i++) {
          // Create a box
          const boxBody = new CANNON.Body({
            mass: mass,
            shape: boxShape,
            position: new CANNON.Vec3(-(N - i - N / 2) * (size * 2 + 2 * space), size * 6 + space, 0),
          })
          world.addBody(boxBody)
          demo.addVisual(boxBody)

          if (previous) {
            // Connect the current body to the last one
            const lockConstraint = new CANNON.LockConstraint(boxBody, previous)
            world.addConstraint(lockConstraint)
          }

          // To keep track of which body was added last
          previous = boxBody
        }

        // Create stands
        const body1 = new CANNON.Body({
          mass: 0,
          shape: boxShape,
          position: new CANNON.Vec3(-(-N / 2 + 1) * (size * 2 + 2 * space), size * 3, 0),
        })
        world.addBody(body1)
        demo.addVisual(body1)

        const body2 = new CANNON.Body({
          mass: 0,
          shape: boxShape,
          position: new CANNON.Vec3(-(N / 2) * (size * 2 + space * 2), size * 3, 0),
        })
        world.addBody(body2)
        demo.addVisual(body2)
      })

      // We link together boxes in a chain
      demo.addScene('Links', () => {
        const world = setupWorld(demo)
        world.gravity.set(0, -20, -1)

        const size = 1
        let mass = 0
        const space = size * 0.1

        const boxShape = new CANNON.Box(new CANNON.Vec3(size, size, size * 0.1))

        const N = 10
        let previous
        for (let i = 0; i < N; i++) {
          // Create a box
          const boxBody = new CANNON.Body({ mass })
          boxBody.addShape(boxShape)
          boxBody.position.set(0, (N - i) * (size * 2 + space * 2) + size * 2 + space, 0)
          boxBody.linearDamping = 0.01 // Damping makes the movement slow down with time
          boxBody.angularDamping = 0.01
          world.addBody(boxBody)
          demo.addVisual(boxBody)

          if (i != 0) {
            // Connect the current body to the last one
            // We connect two corner points to each other.
            const pointConstraint1 = new CANNON.PointToPointConstraint(
              boxBody,
              new CANNON.Vec3(size, size + space, 0),
              previous,
              new CANNON.Vec3(size, -size - space, 0)
            )
            const pointConstraint2 = new CANNON.PointToPointConstraint(
              boxBody,
              new CANNON.Vec3(-size, size + space, 0),
              previous,
              new CANNON.Vec3(-size, -size - space, 0)
            )

            world.addConstraint(pointConstraint1)
            world.addConstraint(pointConstraint2)
          } else {
            // First body is now static. The rest should be dynamic.
            mass = 0.3
          }

          // To keep track of which body was added last
          previous = boxBody
        }
      })

      // Particle cloth on sphere
      demo.addScene('Cloth on sphere', () => {
        const world = setupWorld(demo)

        const dist = 0.2
        const mass = 0.5
        // To construct the cloth we need rows*cols particles.
        const rows = 15
        const cols = 15

        const bodies = {} // bodies['i j'] => particle
        for (let i = 0; i < cols; i++) {
          for (let j = 0; j < rows; j++) {
            // Create a new body
            const body = new CANNON.Body({ mass })
            body.addShape(new CANNON.Particle())
            body.position.set(-(i - cols * 0.5) * dist, 5, (j - rows * 0.5) * dist)
            bodies[`${i} ${j}`] = body
            world.addBody(body)
            demo.addVisual(body)
          }
        }

        // To connect two particles, we use a distance constraint. This forces the particles to be at a constant distance from each other.
        function connect(i1, j1, i2, j2) {
          const distanceConstraint = new CANNON.DistanceConstraint(bodies[`${i1} ${j1}`], bodies[`${i2} ${j2}`], dist)
          world.addConstraint(distanceConstraint)
        }

        for (let i = 0; i < cols; i++) {
          for (let j = 0; j < rows; j++) {
            // Connect particle at position (i,j) to (i+1,j) and to (i,j+1).
            if (i < cols - 1) connect(i, j, i + 1, j)
            if (j < rows - 1) connect(i, j, i, j + 1)
          }
        }

        // Add the static sphere we throw the cloth on top of
        const sphere = new CANNON.Sphere(1.5)
        const body = new CANNON.Body({ mass: 0 })
        body.addShape(sphere)
        body.position.set(0, 3.5, 0)
        world.addBody(body)
        demo.addVisual(body)
      })

      // A pendulum made out of two spheres using a PointToPointConstraint
      demo.addScene('Sphere pendulum', () => {
        const world = setupWorld(demo)

        const size = 1
        const mass = 1

        const sphereShape = new CANNON.Sphere(size)

        const spherebody = new CANNON.Body({ mass })
        spherebody.addShape(sphereShape)
        spherebody.position.set(0, size * 3, 0)
        spherebody.velocity.set(-5, 0, 0)
        spherebody.linearDamping = 0
        spherebody.angularDamping = 0
        world.addBody(spherebody)
        demo.addVisual(spherebody)

        const spherebody2 = new CANNON.Body({ mass: 0 })
        spherebody2.addShape(sphereShape)
        spherebody2.position.set(0, size * 7, 0)
        world.addBody(spherebody2)
        demo.addVisual(spherebody2)

        // Connect this body to the last one
        const pointConstraint = new CANNON.PointToPointConstraint(
          spherebody,
          new CANNON.Vec3(0, size * 2, 0),
          spherebody2,
          new CANNON.Vec3(0, -size * 2, 0)
        )
        world.addConstraint(pointConstraint)
      })

      // Sphere chain
      demo.addScene('Sphere chain', () => {
        const world = setupWorld(demo)
        // world.solver.setSpookParams(1e20, 3)

        const size = 0.5
        const dist = size * 2 + 0.12
        const mass = 1
        const N = 20

        world.solver.iterations = N // To be able to propagate force throw the chain of N spheres, we need at least N solver iterations.

        const sphereShape = new CANNON.Sphere(size)

        let previous
        for (let i = 0; i < N; i++) {
          // Create a new body
          const sphereBody = new CANNON.Body({ mass: i === 0 ? 0 : mass })
          sphereBody.addShape(sphereShape)
          sphereBody.position.set(0, dist * (N - i), 0)
          sphereBody.velocity.x = -i
          world.addBody(sphereBody)
          demo.addVisual(sphereBody)

          // Connect this body to the last one added
          if (previous) {
            const distanceConstraint = new CANNON.DistanceConstraint(sphereBody, previous, dist)
            world.addConstraint(distanceConstraint)
          }

          // Keep track of the lastly added body
          previous = sphereBody
        }
      })

      // Particle cloth. Same as the previous cloth but here we make the first row of particles static, nailing the cloth it in space
      demo.addScene('Particle cloth', () => {
        const world = setupWorld(demo)
        // world.solver.setSpookParams(1e20, 3)
        world.solver.iterations = 18

        const dist = 0.2
        const mass = 0.5
        const rows = 15
        const cols = 15

        const bodies = {} // bodies['i j'] => particle
        for (let i = 0; i < cols; i++) {
          for (let j = 0; j < rows; j++) {
            // Create a new body
            const body = new CANNON.Body({ mass: j == rows - 1 ? 0 : mass })
            body.addShape(new CANNON.Particle())
            body.position.set(-dist * i, dist * j + 5, 0)
            body.velocity.set(0, 0, (Math.sin(i * 0.1) + Math.sin(j * 0.1)) * 3)
            bodies[`${i} ${j}`] = body
            world.addBody(body)
            demo.addVisual(body)
          }
        }

        function connect(i1, j1, i2, j2) {
          const distanceConstraint = new CANNON.DistanceConstraint(bodies[`${i1} ${j1}`], bodies[`${i2} ${j2}`], dist)
          world.addConstraint(distanceConstraint)
        }

        for (let i = 0; i < cols; i++) {
          for (let j = 0; j < rows; j++) {
            if (i < cols - 1) connect(i, j, i + 1, j)
            if (j < rows - 1) connect(i, j, i, j + 1)
          }
        }
      })

      // Particle 3d object
      // Distance constraints can be used to construct even cooler things, like this 3d block.
      demo.addScene('3D cloth structure', () => {
        const world = setupWorld(demo)

        // Max solver iterations: Use more for better force propagation, but keep in mind that it's not very computationally cheap!
        world.solver.iterations = 10

        const dist = 1
        const mass = 1
        const Nx = 6
        const Ny = 3
        const Nz = 3

        const bodies = {} // bodies['i j k'] => particle
        for (let i = 0; i < Nx; i++) {
          for (let j = 0; j < Ny; j++) {
            for (let k = 0; k < Nz; k++) {
              // Create a new body
              const body = new CANNON.Body({ mass })
              body.addShape(new CANNON.Particle())
              body.position.set(-dist * i, dist * k + dist * Nz * 0.3 + 1, dist * j)
              body.velocity.set(0, 0, (Math.sin(i * 0.1) + Math.sin(j * 0.1)) * 30)
              bodies[`${i} ${j} ${k}`] = body
              world.addBody(body)
              demo.addVisual(body)
            }
          }
        }

        function connect(i1, j1, k1, i2, j2, k2, distance) {
          const distanceConstraint = new CANNON.DistanceConstraint(
            bodies[`${i1} ${j1} ${k1}`],
            bodies[`${i2} ${j2} ${k2}`],
            distance
          )
          world.addConstraint(distanceConstraint)
        }

        for (let i = 0; i < Nx; i++) {
          for (let j = 0; j < Ny; j++) {
            for (let k = 0; k < Nz; k++) {
              // normal directions
              if (i < Nx - 1) connect(i, j, k, i + 1, j, k, dist)
              if (j < Ny - 1) connect(i, j, k, i, j + 1, k, dist)
              if (k < Nz - 1) connect(i, j, k, i, j, k + 1, dist)

              // Diagonals
              if (i < Nx - 1 && j < Ny - 1 && k < Nz - 1) {
                // 3d diagonals
                connect(i, j, k, i + 1, j + 1, k + 1, Math.sqrt(3) * dist)
                connect(i + 1, j, k, i, j + 1, k + 1, Math.sqrt(3) * dist)
                connect(i, j + 1, k, i + 1, j, k + 1, Math.sqrt(3) * dist)
                connect(i, j, k + 1, i + 1, j + 1, k, Math.sqrt(3) * dist)
              }

              // 2d diagonals
              if (i < Nx - 1 && j < Ny - 1) {
                connect(i + 1, j, k, i, j + 1, k, Math.sqrt(2) * dist)
                connect(i, j + 1, k, i + 1, j, k, Math.sqrt(2) * dist)
              }
              if (i < Nx - 1 && k < Nz - 1) {
                connect(i + 1, j, k, i, j, k + 1, Math.sqrt(2) * dist)
                connect(i, j, k + 1, i + 1, j, k, Math.sqrt(2) * dist)
              }
              if (j < Ny - 1 && k < Nz - 1) {
                connect(i, j + 1, k, i, j, k + 1, Math.sqrt(2) * dist)
                connect(i, j, k + 1, i, j + 1, k, Math.sqrt(2) * dist)
              }
            }
          }
        }
      })

      demo.start()

      function setupWorld(demo) {
        // Create world
        const world = demo.getWorld()
        world.gravity.set(0, -40, 0)

        // Static ground plane
        const groundShape = new CANNON.Plane()
        const groundBody = new CANNON.Body({ mass: 0 })
        groundBody.addShape(groundShape)
        groundBody.position.set(0, 1, 0)
        groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
        world.addBody(groundBody)
        demo.addVisual(groundBody)

        return world
      }
    </script>
  </body>
</html>
